package BusinessLogic;

import BusinessLogic.Exceptions.*;
import Domain.Exceptions.UserNotLoggedInException;
import Domain.*;
import Domain.DataStructures.IMovieData;
import Domain.DataStructures.IUserData;
import Persistance.Exceptions.UnableToPredictRankException;
import Persistance.PasswordService;
import Persistance.SortedList;
import Persistance.PageSplitter;
import org.apache.log4j.Logger;

import java.security.NoSuchAlgorithmException;
import java.util.*;


/**
 * provides a simplified interface for the system 
 */
public class ControllerFacade {

    private MovieHandler            m_movieHandler;
    private UserHandler             m_userHandler;
    private RecommendationHandler   m_recommendationHandler;
    private DataController          m_dataController;

    static Logger logger            = Logger.getLogger("MovieSystem.BussinessLogic.ControllerFacade");

    public ControllerFacade(){
        m_dataController = DataController.getInstance();
        m_movieHandler          = m_dataController.getMovieHandler();
        m_recommendationHandler = m_dataController.getRecommendationHandler();
        m_userHandler           = m_dataController.getUserHandler();
        logger.trace("Started new terminal "+this);
    }


    /**
     * perform login operation of user with password.
     * @param username
     * @param password
     * @throws AuthenticationFailedException in case of failure
     */
    public void login(String username, String password) throws AuthenticationFailedException {
        try{
            String encPass = PasswordService.encrypt(password);
            m_userHandler.loginAuthenticate(this,username,encPass);            
            logger.info("Login request of user: "+ username+ " Succeeded");
        }
        catch (AuthenticationFailedException e) {
            logger.info("Login request failed for username " +username+ ", " + e.getMessage());
            throw e;
        }
        catch (NoSuchAlgorithmException e) {
            throw new EncryptionException();
        }
    }

   /**
     * perform logout operation of current user.
     * @throws AuthenticationFailedException in case of failure
     */
    public void logout() throws AuthenticationFailedException{
        String username = getCurrentUser().getUsername();
        try {
            m_userHandler.logout(username);            
            logger.info("Logout request of user: "+ username+ " Succeeded");
        }
        catch (AuthenticationFailedException e){
            logger.error("Logout request failed: "+ e.getMessage());
            throw e;
        }
    }

    /**
     * add new user to the system data
     * @param username
     * @param firstname
     * @param lastname
     * @param password
     * @param VIP
     * @param email
     * @param acl
     * @throws AuthenticationFailedException
     * @throws UserAlreadyExistException
     */
    public synchronized void addNewUser(String username, String firstname, String lastname, String password, String VIP, String email, String acl) throws AuthenticationFailedException, UserAlreadyExistException {
        try {
            boolean vip = false;
            String ACL = acl;
            String encPass = PasswordService.encrypt(password);
            logger.info("add new user to system data: "+username);

            if(VIP != null) vip = Boolean.parseBoolean(VIP);
            if(acl == null) ACL = "User";

            if(!ACL.equals("User") && !getCurrentUser().isAdministrator())
                throw new UnsupportedOperationException();
            
            m_userHandler.addNewUser(username, firstname, lastname, encPass, vip, email, ACL);
        }
        catch (NoSuchAlgorithmException e) {
            throw new EncryptionException();
        }
    }

    /**
     * add new movie recommendation of current user to the system data
     * @param moviename
     * @param rank
     * @param freetext
     * @throws MovieNotExistException
     * @throws UserNotLoggedInException
     */
    public synchronized void addMovieRecommendation(String moviename, int rank, String freetext) throws MovieNotExistException, UserNotLoggedInException {
        logger.debug("Adding new recommendation to Movie: " + moviename);
    	String username = getCurrentUser().getUsername();

        if (m_movieHandler.existMovie(moviename)){
            m_recommendationHandler.addMovieRecommendation(username,moviename,rank,freetext);
        }
        else
            throw new MovieNotExistException("failed adding new recommendation for movie:" +moviename);
    }

    /**
     * add new movie to the system data
     * @param name
     * @param director
     * @param year
     * @param length
     * @param country
     * @param genres
     * @param actors
     * @throws DataAlreadyExistException
     */
    public synchronized void addNewMovie(String name,String director,String year,int length,String country,String genres,String actors) throws DataAlreadyExistException {
        logger.debug("Adding new Movie to system data: " + name);
        m_movieHandler.addNewMovie(name,director,year,length,country,genres,actors);
    }

    /**
     * check who is the current user in the terminal
     * @return  current user in the terminal
     * @throws UserNotLoggedInException
     */
    public IUserData getCurrentUser() throws UserNotLoggedInException {
        User currentUser = m_userHandler.getCurrentUser(this);
        if(currentUser == null)
            throw new UserNotLoggedInException();
        return currentUser;
    }

    /**
     * collect all the movies in the system data
     * @return collection of movie names
     */
    public Collection<String> getAllMovies() {
        Collection<Movie> movies = m_movieHandler.getAllMovies();
        Collection<String> res = new LinkedList<String>();
        for (Movie m: movies)
            res.add(m.getName());
        return res; 
    }

    /**
     *  find all online users
     * @return collection of online users names
     */
    public Collection<String> getOnlineUsers(){
        return m_userHandler.getOnlineUsers();
    }

    /**
     * find movie with movieName
     * @param movieName
     * @return IMovieData
     */
    public IMovieData getMovie(String movieName){
        return m_movieHandler.get(movieName);
    }

    /**
     * find User with username
     * @param userName
     * @return IUserData
     */
    public IUserData getUser(String userName){
        return m_userHandler.get(userName);
    }

    /**
     * get all the recommendations of username
     * @param userName
     * @param start
     * @return collection of recommendations
     */
    public Collection<Recommendation> getRecommendationForUser(String userName, int start){
        int amount = m_dataController.getListLength();
        PageSplitter<Recommendation> ps = new PageSplitter<Recommendation>();        
        return ps.splitCollectionToPages(m_recommendationHandler.getUserRecommendations(userName),start,amount);
    }

    /**
     * get all the recommendations of movieName
     * @param movieName
     * @param start
     * @return collection of recommendations
     */
    public Collection getRecommendationForMovie(String movieName, int start){
        int amount = m_dataController.getListLength();
        PageSplitter<Recommendation> ps = new PageSplitter<Recommendation>();
        return ps.splitCollectionToPages(m_recommendationHandler.getMovieRecommendations(movieName),start,amount);
    }

    /**
     * get all the recommendations of movieName
     * @param movieName
     * @return collection of recommendations
     */
    public Collection getRecommendationForMovie(String movieName){     
        return m_recommendationHandler.getMovieRecommendations(movieName);
    }

     /**
     * check if the movie exists in the system data
     * @param moviename
     * @return true if exists, else false
     */
    public boolean existMovie(String moviename){
        return m_movieHandler.existMovie(moviename);
    }


   /**
     * check if the user exists in the system data
     * @param username
     * @return true if exists, else false
     */
    public boolean existUser(String username) {
        return (m_userHandler.get(username)!=null);
    }


    /**
     * check if the cureent user recommendation of movie exists in the system data
     * @param moviename
     * @return true if exists, else false
     */
    public boolean existRecommendation(String moviename) {
        try {
            return (m_recommendationHandler.get(moviename,this.getCurrentUser().getUsername())!=null);
        }
        catch (UserNotLoggedInException e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     *  search users that correspand to keyword
     * @param keyword  filter
     * @param method ,one of SearchMovieMethods
     * @param start  start index
     * @return array of usernames
     * @param startWith
     */
    public String[] searchUser(String keyword, String method,int start, String startWith) {
        int amount = m_dataController.getListLength();
        SearchUserMethods sm = SearchUserMethods.valueOf(method);
        PageSplitter<String> ps = new PageSplitter<String>();
        Collection<String> ans;
        switch(sm){
            case RATED_MOVIE:
                ans = getUsersByMovie(keyword);
                if (startWith != null && !startWith.equals(""))
                    ans = ps.filterStartWith(ans,startWith);
                ans = ps.splitCollectionToPages(ans,start,amount);
                return ans.toArray(new String[ans.size()]);
            case LOGGED:
                ans = getOnlineUsers();
                if (startWith != null && !startWith.equals(""))
                    ans = ps.filterStartWith(ans,startWith);
                ans = ps.splitCollectionToPages(ans,start,amount);
                return ans.toArray(new String[ans.size()]);
        }
        return m_userHandler.searchUser(keyword,sm,start,amount,startWith);
    }


    /**
     * Delete username from system data
     * @param username of user
     */
    public synchronized void deleteUser(String username){
        logger.info("Delete user: username");
        m_userHandler.delete(username);
    }

    /**
     * Delete movie from system data
     * @param movie name
     */
    public synchronized void deleteMovie(String movie){
        logger.info("Delete movie: movie");
        m_movieHandler.delete(movie);
    }

    /**
     * get recommendation count
     * @param user name
     * @return the amount
     */
    public int getRecCount(String user){
        return m_recommendationHandler.getRecCount(user);
    }

    /**
     *  search movies that responds to keyword
     * @param keyword  filter
     * @param method ,one of SearchMovieMethods
     * @param start  start index
     * @return array of movienames
     */
    public String[] searchMovie(String keyword,String method,int start){
        int amount = m_dataController.getListLength();
        SearchMovieMethods sm = SearchMovieMethods.valueOf(method);
        return m_movieHandler.searchMovie(keyword,sm,start,amount);
    }

    /**
     *  search movies that responds to keyword
     * @param keyword  filter
     * @param method ,one of SearchMovieMethods
     * @param start  start index
     * @param startWith A prefix that the movie name starts with
     * @return array of movienames
     */
    public String[] searchMovie(String keyword,String method,int start,String startWith){
        int amount = m_dataController.getListLength();
        SearchMovieMethods sm = SearchMovieMethods.valueOf(method);
        return m_movieHandler.searchMovie(keyword,sm,start,amount,startWith);
    }

    /**
     * collect all the movies that was NOT recommended yet by current user
     * @return collection of movie names to recommend.
    */
    public Collection<String> getUnrecomendedUserMovies() {
        logger.debug("start collecting all the movies that was NOT recommended yet by current user");
        Collection<Movie> movies = m_movieHandler.getAllMovies();
        Collection<String> ans = new LinkedList<String>();
        for (Movie m: movies)
            ans.add(m.getName());
        try {
            String username = getCurrentUser().getUsername();
            Collection<String> recMovies = m_recommendationHandler.getRecommendedMovies(username);
            for (String m : recMovies)
                ans.remove(m);
        } catch (UserNotLoggedInException e) {
            logger.info("Unable to get movies of the user, returning all");
        }
        return ans;
        //TODO: this function can be removed. only the GUI calls it.
    }

    /**
     * collect all the movies that was NOT recommended yet by current user
     * @param keyword  filter
     * @param method ,one of SearchMovieMethods
     * @param start  start index
     * @return array of movie names to recommend.
     */
    public String[] getUnrecomendedUserMovies(String keyword,String method,int start) {
    	logger.debug("start collecting all the movies that was NOT recommended yet by current user");
        int amount = m_dataController.getListLength();
    	if(keyword==null)
    		keyword="a"; //set defult value
        try {
			String username = getCurrentUser().getUsername();
			String filter= "grupi.movies.name LIKE '" +keyword+ 
	    	"%' AND grupi.movies.name not in(select grupi.recommendations.moviename from grupi.recommendations where username=\""+ username +"\")";
	        return m_movieHandler.searchMovie(keyword,filter,start,amount);
		} 
        catch (UserNotLoggedInException e) {
			e.printStackTrace();
			return null;
		}	
     }


    /**
     * collecting recommendations for the user
     * @return Sorted List of pair<rank,moviename> with the recommendations
     * @throws UserNotLoggedInException
     * @param start
     */
    public SortedList<Double,String> getRecommendation(int start) throws UserNotLoggedInException {
        String username = getCurrentUser().getUsername();
        SortedList<Double,String> predMovies = new SortedList<Double,String>();
        logger.info("Start collecting the right recommendations for the user");
        for (Movie movie : m_movieHandler.getAllMovies()){
            if (m_recommendationHandler.get(movie.getName(),username) == null){
                double pred;
                try {
                    pred = (int) (m_recommendationHandler.getPrediction(movie.getName(),username) + 0.5);
                    logger.info("Prediction ("+username+","+movie+") = "+pred);
                }
                catch(UnableToPredictRankException e) {
                    logger.info("Predication failed. "+e.getMessage()+". using default rank");
                    pred = m_movieHandler.get(movie.getName()).getDefaultRank();
                }
                predMovies.put(pred,movie.getName());
            }
        }

        int amount = m_dataController.getListLength();
        PageSplitter<String> ps = new PageSplitter<String>();
        return ps.splitSortedListToPages(predMovies.desendingEntrySet(),start,amount,new SortedList<Double, String>());
    }

    /**
     * get the list length
     * @return length
     */
    public int getListLength() {
        return m_dataController.getListLength();
    }

    /**
     *  check if currentuser is allowed to viewRecommendations
     * @return true if allowed, else false
     */
    public boolean allowedToViewRecommendations() {
        try {
            return (getRecCount(getCurrentUser().getUsername()) >= m_dataController.getMinimumRecommendations());
        }
        catch (UserNotLoggedInException e) {
            return false;
        }
    }

    /**
     * count how many recommendations are lef for current user to reach the minimum
     * @return the amount needed
     */
    public int getRecommendationsLeft(){
        try {
            return (m_dataController.getMinimumRecommendations() - getRecCount(getCurrentUser().getUsername()));
        }
        catch (UserNotLoggedInException e) {
            return m_dataController.getMinimumRecommendations();
        }
    }

     private Collection<String> getUsersByMovie(String movie){
        Collection<Recommendation> tmp = m_recommendationHandler.getMovieRecommendations(movie);
        Collection<String> ans = new LinkedList<String>();
        int i=0;
        for(Recommendation r : tmp){
            ans.add(r.getUserName());
            i++;
        }
        return ans;
    }

    //****************Reports Methods****************************//
    
    public Collection<String> getUsersRatedLastMonth(int count, int start){
        logger.info("setting list of users rated at least "+count+ "movies in the last month.");
        Collection<String> ans = new LinkedList<String>();
        
        Date lastMonth = this.lastMonth().getTime();
        Date date;
        for(String user : getUsers()){
            int rankedThisMonth = 0;
            for(Recommendation r : m_recommendationHandler.getUserRecommendations(user)){
               date=this.m_recommendationHandler.getRecommendationDate(r);
               if (date!=null && date.after(lastMonth))
                 rankedThisMonth++;
            }
            if(rankedThisMonth >= count)
                ans.add(user);
        }

        int amount = m_dataController.getListLength();        
        PageSplitter<String> ps = new PageSplitter<String>();
        return ps.splitCollectionToPages(ans,start,amount);
    }

    private GregorianCalendar lastMonth(){
        GregorianCalendar lastMonth = new GregorianCalendar();
        lastMonth.setTime(new Date());
        lastMonth.add(GregorianCalendar.MONTH, -1);

        return lastMonth;
    }

    public Collection<String> getMostRatedMovies(int count, boolean topDown, int start){
        logger.info("setting list of movies that got most\\least rank of "+count);

        int amount = m_dataController.getListLength();
        PageSplitter<String> ps = new PageSplitter<String>();
        return ps.splitCollectionToPages(m_recommendationHandler.getMostRatedMovies(count, topDown),start,amount);
    }

    public Collection<String> getUsers() {
        return m_userHandler.getUserNames();
    }

    public String[] getUsersByMovieRating(String moviename, int rank,int start) {
        int amount = m_dataController.getListLength();
        logger.info("setting list of users that ranked movie: "+ moviename+ " with rank greater than: "+rank);
        return m_recommendationHandler.getUsersByMovieRating(moviename,rank,start,amount);
    }
    
    public HashMap<String, Integer> getMovieRates(boolean highestRate, int count){

    	HashMap<String, Integer> ans = new HashMap<String, Integer>(); 	
    	Collection<String> movieCollection = m_recommendationHandler.getMoviesByOrderedRate(highestRate);    	
    	int rate = GetNthRank(count, movieCollection, highestRate);
    	int movieCount = 0; 	

    	for (Iterator iterator = movieCollection.iterator(); iterator.hasNext();) {
			String moviename = (String) iterator.next();
			if(!ans.containsKey(moviename)){
				Collection<Recommendation> recCollection = getRecommendationForMovie(moviename);
				int rateCount = 1;
				for (Iterator iterator2 = recCollection.iterator(); iterator2.hasNext();) {
					Recommendation name = (Recommendation) iterator2.next();
					int userRate = name.getUserRank();
					if(highestRate)	{
						if(userRate >= rate)
							rateCount++;
					}
					else
						if(userRate <= rate)
							rateCount++;
					
				}
				if(rateCount > 0){
					ans.put(moviename, new Integer(rateCount));
					if(++movieCount == count){
						return ans;
					}
				}
			}
		}
    	return ans;
    }

	private int GetNthRank(int n, Collection<String> movieCollection, boolean highest) {
		// TODO Auto-generated method stub
		int count = 0;
		int index = 0;
		Vector<String> v = new Vector<String>();
		String movie = "";
		while(index < movieCollection.size() && count < n){
			movie = (String) ((LinkedList)movieCollection).get(index);
			if(!v.contains(movie))
			{
				v.add(movie);
				count++;
			}
			index++;
		}
		if(highest)
			return  m_recommendationHandler.GetHighestRate(movie);
		else
			return m_recommendationHandler.GetLowestRate(movie);
	}


}


